#include <QJsonObject>
#include <QJsonArray>

#include <osg/Geometry>
#include <osg/BlendFunc>

#include <BRepPrimAPI_MakeCylinder.hxx>
#include <gp_Ax2.hxx>
#include <TopExp_Explorer.hxx> 
#include <TopoDS.hxx>

#include "Cylinder.h"
#include "JsonReader.h"
#include "OsgUtil.h"
#include "OcctOsg.h"
#include "GetTriangles.h"
#include "Surface.h"

struct CylinderPrivate
{
	CylinderPrivate()
		: m_rep3D(new osg::Geometry())
	{
		m_rep3D->setNodeMask(D3SurfaceMask);
		auto ss = m_rep3D->getOrCreateStateSet();
		ss->setMode(GL_CULL_FACE, osg::StateAttribute::ON);
		ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
		static osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		ss->setAttributeAndModes(blendFunc);
	}
	osg::ref_ptr<osg::Geometry> m_rep3D;
	osg::Vec3d m_bottomCenter;
	osg::Vec3d m_axis = osg::Vec3d(0, 0, 1);
	double m_radius;
	double m_height;
	bool m_selected = false;
};

Cylinder::Cylinder(INode* parent)
	: Ent3DFace(parent)
	, d_ptr(new CylinderPrivate)
{
}

void Cylinder::setBottomCenter(const osg::Vec3d& pos)
{
	d_ptr->m_bottomCenter = pos;
	addFlags(UPDATE_GEOMETRY);
}

void Cylinder::setAxis(const osg::Vec3d & axis)
{
	osg::Vec3d a(axis);
	double len = a.normalize();
	if (len > 0)
	{
		d_ptr->m_axis = a;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Cylinder::setRadius(double radius)
{
	d_ptr->m_radius = radius;
	addFlags(UPDATE_GEOMETRY);
}

void Cylinder::setHeight(double height)
{
	d_ptr->m_height = height;
	addFlags(UPDATE_GEOMETRY);
}

void setTrianglesNormals(osg::Geometry& geoset);
void Cylinder::update3D()
{
	if (flags())
	{
		std::vector<osg::Vec3d> bottomCirclePoints = getCirclePoints(d_ptr->m_bottomCenter, d_ptr->m_axis, d_ptr->m_radius, 90);
		std::vector<osg::Vec3d> topCirclePoints;
		topCirclePoints.reserve(bottomCirclePoints.size());
		for (auto pt : bottomCirclePoints)
		{
			pt += d_ptr->m_axis * d_ptr->m_height;
			topCirclePoints.push_back(pt);
		}
		auto va = new osg::Vec3Array();
		auto triangles = getDiskTriangles(d_ptr->m_bottomCenter, bottomCirclePoints, true);
		appendPoints(va, triangles);
		auto topCenter(d_ptr->m_bottomCenter);
		topCenter.z() += d_ptr->m_height;
		triangles = getDiskTriangles(topCenter, topCirclePoints, false);
		appendPoints(va, triangles);
		triangles = getCylinderSideTriangles(bottomCirclePoints, topCirclePoints);
		appendPoints(va, triangles);
		d_ptr->m_rep3D->setVertexArray(va);

		osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array();
		if (d_ptr->m_selected)
			ca->push_back(g_selectedFaceColor);
		else
			ca->push_back(g_faceColor);

		d_ptr->m_rep3D->setColorArray(ca.get(), osg::Array::BIND_OVERALL);

		d_ptr->m_rep3D->getPrimitiveSetList().clear();
		d_ptr->m_rep3D->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, va->size()));

		//osgUtil::SmoothingVisitor::smooth(*(d_ptr->m_rep3D.get()));//自动生成法线
		setTrianglesNormals(*d_ptr->m_rep3D);
		d_ptr->m_rep3D->dirtyGLObjects();
		removeFlags(0xFFFFFFFF);
	}
}

osg::Node * Cylinder::getRep3D() const
{
	return d_ptr->m_rep3D.get();
}

void Cylinder::setSelected(bool val)
{
	if (d_ptr->m_selected != val)
	{
		d_ptr->m_selected = val;
		addFlags(UPDATE_GEOMETRY);
	}
}

void Cylinder::save(QJsonObject & json) const
{
	Ent::save(json);

	json["bottomCenter"] = saveVec3f(d_ptr->m_bottomCenter);
	json["axis"] = saveVec3f(d_ptr->m_axis);
	json["radius"] = d_ptr->m_radius;
	json["height"] = d_ptr->m_height;
}

void Cylinder::load(const QJsonObject & json, JsonReader & reader)
{
	Ent::load(json, reader);

	d_ptr->m_bottomCenter = loadVec3f(json["bottomCenter"].toArray());
	if (json.contains("axis"))
		d_ptr->m_axis = loadVec3f(json["axis"].toArray());
	d_ptr->m_radius = json["radius"].toDouble();
	d_ptr->m_height = json["height"].toDouble();
}

std::vector<osg::Vec3d> Cylinder::handles() const
{
	std::vector<osg::Vec3d> result;
	result.push_back(d_ptr->m_bottomCenter);
	osg::Vec3d topCenter(d_ptr->m_bottomCenter);
	topCenter += d_ptr->m_axis * d_ptr->m_height;
	result.push_back(topCenter);
	return result;
}

void Cylinder::move(const osg::Vec3d & offset)
{
	d_ptr->m_bottomCenter += offset;
	addFlags(UPDATE_GEOMETRY);
}

std::shared_ptr<Ent> Cylinder::clone() const
{
	auto cylinder = std::make_shared<Cylinder>(getParent());
	cylinder->copy(*this);
	return cylinder;
}

void Cylinder::copy(const Ent & ent)
{
	auto cylinder = dynamic_cast<const Cylinder*>(&ent);
	if (cylinder)
	{
		d_ptr->m_bottomCenter = cylinder->d_ptr->m_bottomCenter;
		d_ptr->m_axis = cylinder->d_ptr->m_axis;
		d_ptr->m_radius = cylinder->d_ptr->m_radius;
		d_ptr->m_height = cylinder->d_ptr->m_height;
		addFlags(UPDATE_GEOMETRY);
	}
}

std::set<std::shared_ptr<INode>> Cylinder::decompose() const
{
	std::set<std::shared_ptr<INode>> result;
	gp_Pnt pt(d_ptr->m_bottomCenter.x(), d_ptr->m_bottomCenter.y(), d_ptr->m_bottomCenter.z());
	osg::Vec3d xAxis, yAxis;
	ArbitraryAxis(d_ptr->m_axis, xAxis, yAxis);
	gp_Dir Vn = toDir(d_ptr->m_axis);
	gp_Dir Vx = toDir(xAxis);
	gp_Ax2 ax2(pt, Vn, Vx);
	auto cylinder = BRepPrimAPI_MakeCylinder(ax2, d_ptr->m_radius, d_ptr->m_height);
	TopExp_Explorer faceExp;
	for (faceExp.Init(cylinder, TopAbs_FACE); faceExp.More(); faceExp.Next())
	{
		TopoDS_Face face = TopoDS::Face(faceExp.Current());
		auto surface = std::make_shared<Surface>(getParent());
		surface->setShape(face);
		result.insert(surface);
	}
	return result;
}

void Cylinder::rotate(const double angle, const osg::Vec2d & basePoint)
{
	rotate2D(basePoint.x(), basePoint.y(), angle, d_ptr->m_bottomCenter);
	rotate2D(0, 0, angle, d_ptr->m_axis);
	addFlags(UPDATE_GEOMETRY);
}
	
